{/* Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:@react-spectrum/tabs';
import utilsDocs from 'docs:@react-aria/utils';
import {HeaderInfo, PropTable, PageDescription, TypeLink, ClassAPI, VersionBadge} from '@react-spectrum/docs';
import packageData from '@react-spectrum/tabs/package.json';
import tabsUtils from 'docs:@react-aria/test-utils/src/tabs.ts';

```jsx import
import {ActionGroup} from '@react-spectrum/actiongroup';
import Bookmark from '@spectrum-icons/workflow/Bookmark';
import {Button} from '@react-spectrum/button';
import Calendar from '@spectrum-icons/workflow/Calendar';
import Dashboard from '@spectrum-icons/workflow/Dashboard';
import {Flex} from '@react-spectrum/layout';
import {Text} from '@react-spectrum/text';
import {Item, Tabs, TabList, TabPanels} from '@react-spectrum/tabs';
```

---
category: Navigation
keywords: [tabs, collections]
---

# Tabs

<PageDescription>{docs.exports.Tabs.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['Item', 'TabList', 'TabPanels', 'Tabs']}
  sourceData={[
    {type: 'Spectrum', url: 'https://spectrum.adobe.com/page/tabs/'}
  ]}
  since="3.11.0" />

## Example
```tsx example
<Tabs aria-label="History of Ancient Rome">
  <TabList>
    <Item key="FoR">Founding of Rome</Item>
    <Item key="MaR">Monarchy and Republic</Item>
    <Item key="Emp">Empire</Item>
  </TabList>
  <TabPanels>
    <Item key="FoR">
      Arma virumque cano, Troiae qui primus ab oris.
    </Item>
    <Item key="MaR">
      Senatus Populusque Romanus.
    </Item>
    <Item key="Emp">
      Alea jacta est.
    </Item>
  </TabPanels>
</Tabs>
```

## Content
Tabs expects `<TabList>` and `<TabPanels>` elements as children, however additional elements may wrap them to allow for layout flexibility. TabList and TabPanels follow the [Collection Components](collections.html) API, accepting both static and dynamic collections. TabList and TabPanel accept `<Item>` elements as children,
each with a `key` prop. The key passed to the TabList `<Item>` must match its corresponding TabPanel `<Item>`.
Static collections, as in the previous example, can be used when the full list of tabs and their contents is known ahead of time.

Dynamic collections, as shown below, can be used when the options come from an external data source such as an API call, or update over time. As seen below, an iterable list of options is passed to the Tabs using the `items` prop.
Each item accepts a key prop, which is passed to the `onSelectionChange` handler to identify the selected item. Alternatively, if the item objects contain an `id` property, as shown in the example below, then this is used automatically and a `key` prop is not required.
See the [Events](#events) section for more detail on selection.

```tsx example
import type {Key} from '@adobe/react-spectrum';

function Example() {
  let tabs = [
    {id: 1, name: 'Founding of Rome', children: 'Arma virumque cano, Troiae qui primus ab oris.'},
    {id: 2, name: 'Monarchy and Republic', children: 'Senatus Populusque Romanus.'},
    {id: 3, name: 'Empire', children: 'Alea jacta est.'}
  ];
  type Tab = typeof tabs[0];
  let [tabId, setTabId] = React.useState<Key>(1);

  return (
    <>
      <p>Current tab id: {tabId}</p>
      <Tabs aria-label="History of Ancient Rome" items={tabs} onSelectionChange={setTabId}>
        <TabList>
          {(item: Tab) => (
            <Item>
              {item.name}
            </Item>
          )}
        </TabList>
        <TabPanels>
          {(item: Tab) => (
            <Item>
              {item.children}
            </Item>
          )}
        </TabPanels>
      </Tabs>
    </>
  );
}
```

### Internationalization
To internationalize Tabs, a localized string should be passed as children to the TabList `<Item>`. Any text content within the Tab's panel should also be localized accordingly.
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of Tabs is automatically flipped.

### Icons in tabs

Icons can also be used in `Tabs` in addition to a text label. Tabs use [Slots](./layout.html#slots) in order to style Icons and Text together.

```tsx example
<Tabs aria-label="History of Ancient Rome">
  <TabList>
    <Item key="FoR" textValue="FoR"><Bookmark /><Text>Founding of Rome</Text></Item>
    <Item key="MaR" textValue="MaR"><Calendar /><Text>Monarchy and Republic</Text></Item>
    <Item key="Emp" textValue="Emp"><Dashboard /><Text>Empire</Text></Item>
  </TabList>
  <TabPanels>
    <Item key="FoR">
      Arma virumque cano, Troiae qui primus ab oris.
    </Item>
    <Item key="MaR">
      Senatus Populusque Romanus.
    </Item>
    <Item key="Emp">
      Alea jacta est.
    </Item>
  </TabPanels>
</Tabs>
```

### Customizing layout

If you need to add additional structure to `Tabs` such as buttons in-line with the `TabList`, we support this too.
`TabList` and `TabPanels` are not required to be the immediate children of `Tabs`.

```tsx example
function Example () {
  let [tabs, setTabs] = React.useState([
    {name: 'Tab 1', children: 'Tab Body 1'},
    {name: 'Tab 2', children: 'Tab Body 2'},
    {name: 'Tab 3', children: 'Tab Body 3'}
  ]);
  type Tab = typeof tabs[0];

  let addTab = () => {
    let newTabs = [...tabs];
    newTabs.push({
      name: `Tab ${tabs.length + 1}`,
      children: `Tab Body ${tabs.length + 1}`
    });

    setTabs(newTabs);
  };

  let removeTab = () => {
    if (tabs.length > 1) {
      let newTabs = [...tabs];
      newTabs.pop();
      setTabs(newTabs);
    }
  };

  return (
    <Tabs aria-label="Tab example" items={tabs}>
      <Flex>
        <TabList flex="1 1 auto" minWidth="0px">
          {(item: Tab) => (
            <Item key={item.name}>
              {item.name}
            </Item>
          )}
        </TabList>
        <div style={{display: 'flex', flex: '0 0 auto', borderBottom: 'var(--spectrum-alias-border-size-thick) solid var(--spectrum-global-color-gray-300)'}}>
          <ActionGroup disabledKeys={tabs.length === 1 ? ['remove'] : undefined} onAction={val => val === 'add' ? addTab() : removeTab()}>
            <Item key="add">
              Add Tab
            </Item>
            <Item key="remove">
              Remove Tab
            </Item>
          </ActionGroup>
        </div>
      </Flex>
      <TabPanels>
        {(item: Tab) => (
          <Item key={item.name}>
            {item.children}
          </Item>
        )}
      </TabPanels>
    </Tabs>
  );
}
```

## Labeling
### Accessibility
While an `aria-label` is not explicitly required for a tab list, Tabs should be labeled using a `aria-label` in the absence of an ancestor [landmark](https://www.w3.org/WAI/GL/wiki/Using_ARIA_landmarks_to_identify_regions_of_a_page).
This will prevent screen readers from announcing non-focused tabs, allowing for a more focused experience.

## Selection
Setting a selected tab can be done by using the `defaultSelectedKey` or `selectedKey` prop. The selected key corresponds to the `key` of an item. See [Events](#events) for more details on selection events.
Additionally, see the `react-stately` [Selection docs](selection.html#selected-key-data-type) for caveats regarding selection prop typing.

```tsx example
function Example() {
  let tabs = [
    {id: 1, name: 'Keyboard Settings', children: 'No keyboard detected.'},
    {id: 2, name: 'Mouse Settings', children: 'No mouse detected.'},
    {id: 3, name: 'Gamepad Settings', children: 'No gamepad detected'}
  ];
  type Tab = typeof tabs[0];
  let [tab, setTab] = React.useState<Key>(2);

  return (
    <Flex gap="size-150" wrap>
      <span id="label-2">Settings (uncontrolled)</span>
      <Tabs aria-labelledby="label-2" items={tabs} defaultSelectedKey={2} marginBottom="size-400">
        <TabList>
          {(item: Tab) => (
            <Item>
              {item.name}
            </Item>
          )}
        </TabList>
        <TabPanels>
          {(item: Tab) => (
            <Item>
              {item.children}
            </Item>
          )}
        </TabPanels>
      </Tabs>
      <span id="label-3">Settings (controlled)</span>
      <Tabs aria-labelledby="label-3" items={tabs} selectedKey={tab} onSelectionChange={setTab}>
        <TabList>
          {(item: Tab) => (
            <Item>
              {item.name}
            </Item>
          )}
        </TabList>
        <TabPanels>
          {(item: Tab) => (
            <Item>
              {item.children}
            </Item>
          )}
        </TabPanels>
      </Tabs>
    </Flex>
  );
}
```

## Events
Tabs supports selection via mouse, keyboard, and touch. You can handle all of these via the `onSelectionChange` prop. Tabs will pass the selected `key` to the `onSelectionChange` handler.

The following example uses an `onSelectionChange` handler to update the tab selection stored in React state.

```tsx example
function Example() {
  let tabs = [
    {name: 'Triassic', children: 'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'},
    {name: 'Jurassic', children: 'The Jurassic ranges from 200 million years to 145 million years ago.'},
    {name: 'Cretaceous', children: 'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'}
  ];
  type Tab = typeof tabs[0];
  let [timePeriod, setTimePeriod] = React.useState<Key>('Triassic');

  return (
    <>
      <p>Selected time period: {timePeriod}</p>
      <Tabs aria-label="Mesozoic time periods" items={tabs} selectedKey={timePeriod} onSelectionChange={setTimePeriod}>
        <TabList>
          {(item: Tab) => (
            <Item key={item.name}>
              {item.name}
            </Item>
          )}
        </TabList>
        <TabPanels>
          {(item: Tab) => (
            <Item key={item.name}>
              {item.children}
            </Item>
          )}
        </TabPanels>
      </Tabs>
    </>
  );
}
```

### Keyboard Activation
By default, pressing the arrow keys while focus is on a Tab will switch selection to the adjacent Tab in that direction, updating the content displayed accordingly. If you would like to prevent selection change
from happening automatically you can set the `keyboardActivation` prop to "manual". This will prevent tab selection from changing on arrow key press, requiring a subsequent `Enter` or `Space` key press to confirm
tab selection.

```tsx example
function Example() {
  let tabs = [
    {name: 'Triassic', children: 'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'},
    {name: 'Jurassic', children: 'The Jurassic ranges from 200 million years to 145 million years ago.'},
    {name: 'Cretaceous', children: 'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'}
  ];
  type Tab = typeof tabs[0];

  return (
    <Tabs aria-label="Mesozoic time periods" items={tabs} keyboardActivation="manual">
      <TabList>
        {(item: Tab) => (
          <Item key={item.name}>
            {item.name}
          </Item>
        )}
      </TabList>
      <TabPanels>
        {(item: Tab) => (
          <Item key={item.name}>
            {item.children}
          </Item>
        )}
      </TabPanels>
    </Tabs>
  );
}
```

## Links

Tabs may be rendered as links to different routes in your application. This can be achieved by passing the `href` prop to the `<Item>` component. By default, links perform native browser navigation. However, you'll usually want to synchronize the selected tab with the URL from your client side router. This takes two steps:

1. Set up a <TypeLink links={utilsDocs.links} type={utilsDocs.exports.RouterProvider} /> at the root of your app. This will handle link navigation from all React Aria components using your framework or router. See the [client side routing guide](routing.html) to learn how to set this up.
2. Use the `selectedKey` prop to set the selected tab based on the URL, as [described above](#selection).

This example uses [React Router](https://reactrouter.com/en/main) to setup routes for each tab and synchronize the selection with the URL.

```tsx
import {useLocation, useNavigate, BrowserRouter, Routes, Route, Outlet} from 'react-router-dom';
import {RouterProvider} from '@adobe/react-spectrum';

function AppTabs() {
  let {pathname} = useLocation();

  return (
    <Tabs selectedKey={pathname}>
      <TabList aria-label="Tabs">
        <Item key="/" href="/">Home</Item>
        <Item key="/shared" href="/shared">Shared</Item>
        <Item key="/deleted" href="/deleted">Deleted</Item>
      </TabList>
      <TabPanels>
        <Item key="/"><Outlet /></Item>
        <Item key="/shared"><Outlet /></Item>
        <Item key="/deleted"><Outlet /></Item>
      </TabPanels>
    </Tabs>
  );
}

function App() {
  let navigate = useNavigate();
  return (
    <RouterProvider navigate={navigate}>
      <Routes>
        <Route path="/" element={<AppTabs />}>
          <Route index element={<HomePage />} />
          <Route path="/shared" element={<SharedPage />} />
          <Route path="/deleted" element={<DeletedPage />} />
        </Route>
      </Routes>
    </RouterProvider>
  );
}

<BrowserRouter>
  <App />
</BrowserRouter>
```

## Props

### Tabs Props

<PropTable component={docs.exports.Tabs} links={docs.links} style={{marginBottom: '40px'}} />

### TabList Props

<PropTable component={docs.exports.TabList} links={docs.links} style={{marginBottom: '40px'}} />

### TabPanels Props

<PropTable component={docs.exports.TabPanels} links={docs.links} style={{marginBottom: '40px'}} />

## Visual options
### Density
[View guidelines](https://spectrum.adobe.com/page/tabs/#Density)

```tsx example
<Tabs aria-label="Chat log density example" density="compact">
  <TabList>
    <Item key="item1">
      John Doe
    </Item>
    <Item key="item2">
      Jane Doe
    </Item>
    <Item key="item3">
      Joe Bloggs
    </Item>
  </TabList>
  <TabPanels>
    <Item key="item1">
      There is no prior chat history with John Doe.
    </Item>
    <Item key="item2">
      There is no prior chat history with Jane Doe.
    </Item>
    <Item key="item3">
      There is no prior chat history with Joe Bloggs.
    </Item>
  </TabPanels>
</Tabs>
```

### Quiet
[View guidelines](https://spectrum.adobe.com/page/tabs/#Quiet)

```tsx example
<Tabs aria-label="Chat log quiet example" isQuiet>
  <TabList>
    <Item key="item1">
      John Doe
    </Item>
    <Item key="item2">
      Jane Doe
    </Item>
    <Item key="item3">
      Joe Bloggs
    </Item>
  </TabList>
  <TabPanels>
    <Item key="item1">
      There is no prior chat history with John Doe.
    </Item>
    <Item key="item2">
      There is no prior chat history with Jane Doe.
    </Item>
    <Item key="item3">
      There is no prior chat history with Joe Bloggs.
    </Item>
  </TabPanels>
</Tabs>
```

### Disabled
[View guidelines](https://spectrum.adobe.com/page/tabs/#Disabled)

```tsx example
<Flex direction="column" rowGap="size-150">
  <Tabs aria-label="Chat log single tab disabled example" disabledKeys={['item2']}>
    <TabList>
      <Item key="item1">
        John Doe
      </Item>
      <Item key="item2">
        Jane Doe
      </Item>
      <Item key="item3">
        Joe Bloggs
      </Item>
    </TabList>
    <TabPanels>
      <Item key="item1">
        There is no prior chat history with John Doe.
      </Item>
      <Item key="item2">
        There is no prior chat history with Jane Doe.
      </Item>
      <Item key="item3">
        There is no prior chat history with Joe Bloggs.
      </Item>
    </TabPanels>
  </Tabs>
  <Tabs aria-label="Chat log all tabs disabled example" isDisabled>
    <TabList>
      <Item key="item1">
        John Doe
      </Item>
      <Item key="item2">
        Jane Doe
      </Item>
      <Item key="item3">
        Joe Bloggs
      </Item>
    </TabList>
    <TabPanels>
      <Item key="item1">
        There is no prior chat history with John Doe.
      </Item>
      <Item key="item2">
        There is no prior chat history with Jane Doe.
      </Item>
      <Item key="item3">
        There is no prior chat history with Joe Bloggs.
      </Item>
    </TabPanels>
  </Tabs>
</Flex>
```

### Orientation
[View guidelines](https://spectrum.adobe.com/page/tabs/#Orientation)

By default, tabs are horizontally oriented. The `orientation` prop can be set to `vertical` to change this. This does not affect keyboard navigation.

```tsx example
<Tabs aria-label="Chat log orientation example" orientation="vertical">
  <TabList>
    <Item key="item1">
      John Doe
    </Item>
    <Item key="item2">
      Jane Doe
    </Item>
    <Item key="item3">
      Joe Bloggs
    </Item>
  </TabList>
  <TabPanels>
    <Item key="item1">
      There is no prior chat history with John Doe.
    </Item>
    <Item key="item2">
      There is no prior chat history with Jane Doe.
    </Item>
    <Item key="item3">
      There is no prior chat history with Joe Bloggs.
    </Item>
  </TabPanels>
</Tabs>
```

### Collapse (overflow behavior)
[View guidelines](https://spectrum.adobe.com/page/tabs/#Too-many-tabs)

If there isn't enough horizontal room to render every tab on a single line, the component will collapse all tabs into a `Picker`. Note that this does not apply to vertical Tabs.

Try the example below to see the above behavior.

```tsx example
function Example() {
  let [collapse, setCollapse] = React.useState(false)

  return (
    <>
      <div style={{width: collapse ? '150px' : '300px', marginBottom: '50px', height: '150px', maxWidth: '100%'}}>
        <Tabs aria-label="Chat log collapse example">
          <TabList>
            <Item key="item1">
              John Doe
            </Item>
            <Item key="item2">
              Jane Doe
            </Item>
            <Item key="item3">
              Joe Bloggs
            </Item>
          </TabList>
          <TabPanels>
            <Item key="item1">
              There is no prior chat history with John Doe.
            </Item>
            <Item key="item2">
              There is no prior chat history with Jane Doe.
            </Item>
            <Item key="item3">
              There is no prior chat history with Joe Bloggs.
            </Item>
          </TabPanels>
        </Tabs>
      </div>
      <Button variant="primary" onPress={() => setCollapse((collapse) => !collapse)}>
        Toggle tab container size.
      </Button>
    </>
  );
}
```

### Emphasized
[View guidelines](https://spectrum.adobe.com/page/tabs/#Emphasis)

```tsx example
<Tabs aria-label="Chat log emphasized example" isEmphasized>
  <TabList>
    <Item key="item1">
      John Doe
    </Item>
    <Item key="item2">
      Jane Doe
    </Item>
    <Item key="item3">
      Joe Bloggs
    </Item>
  </TabList>
  <TabPanels>
    <Item key="item1">
      There is no prior chat history with John Doe.
    </Item>
    <Item key="item2">
      There is no prior chat history with Jane Doe.
    </Item>
    <Item key="item3">
      There is no prior chat history with Joe Bloggs.
    </Item>
  </TabPanels>
</Tabs>
```

## Testing

### Test utils <VersionBadge version="beta" style={{marginLeft: 4, verticalAlign: 'bottom'}} />

Tabs features automatic tab collapse behavior and may need specific mocks to test said behavior. Please also refer to
[React Spectrum's test suite](https://github.com/adobe/react-spectrum/blob/326f48154e301edab425c8198c5c3af72422462b/packages/%40react-spectrum/tabs/test/Tabs.test.js#L58-L62) if you
run into any issues with your tests.

`@react-spectrum/test-utils` offers common tabs interaction utilities which you may find helpful when writing tests. See [here](./testing.html#react-spectrum-test-utils) for more information on how to setup these utilities
in your tests. Below is the full definition of the tabs tester and a sample of how you could use it in your test suite.

```ts
// Tabs.test.ts
import {render} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/Tabs.html#testing

it('Tabs can change selection via keyboard', async function () {
  // Render your test component/app and initialize the listbox tester
  let {getByTestId} = render(
    <Provider theme={defaultTheme}>
      <Tabs data-testid="test-tabs">
        ...
      </Tabs>
    </Provider>
  );
  let tabsTester = testUtilUser.createTester('Tabs', {root: getByTestId('test-tabs'), interactionType: 'keyboard'});

  let tabs = tabsTester.tabs;
  expect(tabsTester.selectedTab).toBe(tabs[0]);

  await tabsTester.triggerTab({tab: 1});
  expect(tabsTester.selectedTab).toBe(tabs[1]);
});
```

<ClassAPI links={tabsUtils.links} class={tabsUtils.exports.TabsTester} />
